/*
 * LDIR
 *
 * LDIR, a GPS cartography application built for Let's Do It Romania eco
 * campaign.
 * Copyright (C) 2010 Genera Software <office@generasoftware.com>
 *
 * http://generasoftware.com/
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */
// SYSTEM INCLUDES
#include <avkon.hrh>
#include <aknnotewrappers.h>
#include <uri8.h>
#include <http.h>
#include <HttpStringConstants.h>
#include <http\RHTTPTransaction.h>
#include <http\RHTTPSession.h>
#include <http\RHTTPHeaders.h>
#include <commdb.h>				// CCommsDatabase
#include <commdbconnpref.h>		// TCommDbConnPref
#include <es_enum.h>			// TConnectionInfoV2Buf
#include <stringloader.h>
#include <LDIR.RSG>
// USER INCLUDES
#include "ClientEngine.h"
#include "ConnectionOpener.h"
#include "HttpFilter.h"
#include "Settings.h"
#include "Logger.h"

// MACROS & CONSTS
#define KTIMEOUTTIME 90000000
const TInt KDefaultBufferSize = 256;
// LITERALS
// Used user agent for requests
_LIT8(KUserAgent, "ldir-symbian");
// This client accepts all content types.
// (change to e.g. "text/plain" for plain text only)
_LIT8(KAccept, "*/*");
// Most common error; no access point configured, and session creation
// leaves with KErrNotFound.

//////////////////////////////////////////////////////////////////////////
CHttpClient* CHttpClient::NewL( MHttpClientObserver& aObserver )
    {
    CHttpClient* self = CHttpClient::NewLC( aObserver );
    CleanupStack::Pop( self );
    return self;
    }

//////////////////////////////////////////////////////////////////////////
CHttpClient* CHttpClient::NewLC( MHttpClientObserver& aObserver )
    {
    CHttpClient* self = new ( ELeave ) CHttpClient( aObserver );
    CleanupStack::PushL( self );
    self->ConstructL();
    return self;
    }

//////////////////////////////////////////////////////////////////////////
CHttpClient::CHttpClient( MHttpClientObserver& aObserver ) :
    iObserver( aObserver ), iRunning( EFalse )
    {
    }

//////////////////////////////////////////////////////////////////////////
CHttpClient::~CHttpClient()
    {
    delete iConnOpener;
    iHttpSession.Close();
    iConnection.Close();
    iSockServ.Close();
    ResetData();
    }

//////////////////////////////////////////////////////////////////////////
void CHttpClient::ResetData()
    {

    }

//////////////////////////////////////////////////////////////////////////
void CHttpClient::ConstructL()
    {

    // Open RHTTPSession with default protocol ("HTTP/TCP")
    User::LeaveIfError( iSockServ.Connect() );
    User::LeaveIfError( iConnection.Open( iSockServ ) );
    iConnOpener = CConnectionOpener::NewL( iConnection, *this );
    TRAPD(err, iHttpSession.OpenL());
    if ( err != KErrNone )
        {
        iObserver.ClientErrorL( err );
        User::Leave( err );
        }

    // show a dialog when when server certificate needs to be accepted.
    RStringPool stringPool = iHttpSession.StringPool();
    RHTTPConnectionInfo connInfo = iHttpSession.ConnectionInfo();
    connInfo.SetPropertyL( stringPool.StringF( HTTP::ESecureDialog,
        RHTTPSession::GetTable() ), THTTPHdrVal( stringPool.StringF(
        HTTP::EDialogPrompt, RHTTPSession::GetTable() ) ) );

    // Install this class as the callback for authentication requests. When
    // page requires authentication the framework calls GetCredentialsL to get
    // user name and password.
    InstallAuthenticationL( iHttpSession );
    //RemoveValidationFilter();
    //iCustomValidationFilter = new ( ELeave ) CHttpFilter;
    //iCustomValidationFilter->ConstructL( iHttpSession );
    }

////////////////////////////////////////////////////////////////////////////////
void CHttpClient::SetHeaderL( RHTTPHeaders aHeaders, TInt aHdrField,
    const TDesC8& aHdrValue )
    {
    RStringF valStr = iHttpSession.StringPool().OpenFStringL( aHdrValue );
    CleanupClosePushL( valStr );
    THTTPHdrVal val( valStr );
    aHeaders.SetFieldL( iHttpSession.StringPool().StringF( aHdrField,
        RHTTPSession::GetTable() ), val );
    CleanupStack::PopAndDestroy(); // valStr
    }

//////////////////////////////////////////////////////////////////////////
void CHttpClient::SetHeaderL( RHTTPHeaders aHeaders, const TDesC8& aFielName,
    const TDesC8& aFieldValue )
    {
    RStringF valStr = iHttpSession.StringPool().OpenFStringL( aFieldValue );
    CleanupClosePushL( valStr );
    RStringF nameStr = iHttpSession.StringPool().OpenFStringL( aFielName );
    CleanupClosePushL( nameStr );

    THTTPHdrVal val( valStr );
    aHeaders.SetFieldL( nameStr, val );
    CleanupStack::PopAndDestroy( 2 ); // valStr, nameStr
    }

//////////////////////////////////////////////////////////////////////////
void CHttpClient::ConnectL()
    {
    CSettings& settings = CSettings::InstanceL();
    TInt32 iap = settings.Iap();
    TBool connected = EFalse;
    // Not yet connected, start connection
    if ( !connected )
        {
        TCommDbConnPref connectPref;
        //Define preferences for connection
        // User iap version
        LOG(EVerbosityHttp, _L("CHttpClient::ConnectL() - iIap = %d"), iap);
        if ( KErrNotFound != iap )
            {
            connectPref.SetIapId( iap );
            connectPref.SetDirection( ECommDbConnectionDirectionOutgoing );
            connectPref.SetDialogPreference( ECommDbDialogPrefDoNotPrompt );
            }
        else
            {
            // setup preferences
            connectPref.SetDialogPreference( ECommDbDialogPrefPrompt );
            connectPref.SetDirection( ECommDbConnectionDirectionOutgoing );
            }

        //Start Connection
        iOpeningConnection = ETrue;
        iConnOpener->OpenConnection( connectPref );
        return;
        }
    ConnectionCreatedL( KErrNone );
    }

////////////////////////////////////////////////////////////////////////////////
void CHttpClient::ConnectionCreatedL( const TInt& aError )
    {
    iOpeningConnection = EFalse;
    if ( aError != KErrNone )
        {
        TRAPD(error, iObserver.ClientErrorL(aError));
        PanicIfError( error );
        }
    else
        {
        _LIT(KSetting, "IAP\\Id");
        TUint32 iap = KErrNotFound;
        iConnection.GetIntSetting( KSetting, iap );
        LOG(EVerbosityHttp, _L("CHttpClient::ConnectionCreated() - iConnection IAP = %d"), iap);
        CSettings::InstanceL().SetIap( iap );
        CSettings::InstanceL().StoreSettingsL();
        RStringPool strP = iHttpSession.StringPool();
        RHTTPConnectionInfo connInfo = iHttpSession.ConnectionInfo();
        connInfo.SetPropertyL( strP.StringF( HTTP::EHttpSocketServ,
            RHTTPSession::GetTable() ), THTTPHdrVal( iSockServ.Handle() ) );
        TInt connPtr = REINTERPRET_CAST(TInt, &iConnection);
        connInfo.SetPropertyL( strP.StringF( HTTP::EHttpSocketConnection,
            RHTTPSession::GetTable() ), THTTPHdrVal( connPtr ) );
        iObserver.ClientEventL( 1010 ); // connected event
        }
    }

//////////////////////////////////////////////////////////////////////////
void CHttpClient::IssueHTTPPostL( const TDesC8& aUri,
    const TDesC8& aContentType, const TDesC8& aOAuth,
    MHTTPDataSupplier* aSupplier )
    {
    LOG(EVerbosityHttp, _L("CHttpClient::IssueHTTPPostL() - IN."));
    // Parse string to URI
    TUriParser8 uri;
    uri.Parse( aUri );

    // Get request method string for HTTP POST
    RStringF method = iHttpSession.StringPool().StringF( HTTP::EPOST,
        RHTTPSession::GetTable() );

    // Open transaction with previous method and parsed uri. This class will
    // receive transaction events in MHFRunL and MHFRunError.
    iTransaction = iHttpSession.OpenTransactionL( uri, *this, method );

    // Set headers for request; user agent, accepted content type and body's
    // content type.
    RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();
    SetHeaderL( hdr, HTTP::EUserAgent, KUserAgent );
    SetHeaderL( hdr, HTTP::EAccept, KAccept );
    if ( aContentType != KNullDesC8 )
        SetHeaderL( hdr, HTTP::EContentType, aContentType );
    if ( aOAuth != KNullDesC8 )
        SetHeaderL( hdr, HTTP::EAuthorization, aOAuth );
    if ( aSupplier != NULL )
        iTransaction.Request().SetBody( *aSupplier );

    // Submit the transaction. After this the framework will give transaction
    // events via MHFRunL and MHFRunError.
    iTransaction.SubmitL();
    iRunning = ETrue;

    LOG(EVerbosityHttp, _L("CHttpClient::IssueHTTPPostL() - OUT."));
    }

////////////////////////////////////////////////////////////////////////////////
void CHttpClient::IssueHTTPGetL( const TDesC8& aUri,
    const TDesC8& aContentType, const TDesC8& aOAuth,
    MHTTPDataSupplier* aSupplier )
    {
    // Parse string to URI
    TUriParser8 uri;
    uri.Parse( aUri );

    LOG(EVerbosityHttp, _L8("CHttpClient::IssueHTTPGetL() - uri = %S"), &aUri);
    // Get request method string for HTTP POST
    RStringF method = iHttpSession.StringPool().StringF( HTTP::EGET,
        RHTTPSession::GetTable() );

    // Open transaction with previous method and parsed uri. This class will
    // receive transaction events in MHFRunL and MHFRunError.
    iTransaction = iHttpSession.OpenTransactionL( uri, *this, method );

    // Set headers for request; user agent, accepted content type and body's
    // content type.
    RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();
    SetHeaderL( hdr, HTTP::EUserAgent, KUserAgent );
    SetHeaderL( hdr, HTTP::EAccept, KAccept );
    //	if ( aContentType != KNullDesC8 )
    //		SetHeaderL( hdr, HTTP::EContentType, aContentType );
    //	if ( aOAuth != KNullDesC8 )
    //		SetHeaderL( hdr, HTTP::EAuthorization, aOAuth );

    // Submit the transaction. After this the framework will give transaction
    // events via MHFRunL and MHFRunError.
    iTransaction.SubmitL();
    iRunning = ETrue;
    }

////////////////////////////////////////////////////////////////////////////////
void CHttpClient::IssueHTTPDeleteL( const TDesC8& aUri,
    const TDesC8& aContentType, const TDesC8& aOAuth,
    MHTTPDataSupplier* aSupplier )
    {
    // Parse string to URI
    TUriParser8 uri;
    uri.Parse( aUri );

    // Get request method string for HTTP POST
    RStringF method = iHttpSession.StringPool().StringF( HTTP::EDELETE,
        RHTTPSession::GetTable() );

    // Open transaction with previous method and parsed uri. This class will
    // receive transaction events in MHFRunL and MHFRunError.
    iTransaction = iHttpSession.OpenTransactionL( uri, *this, method );

    // Set headers for request; user agent, accepted content type and body's
    // content type.
    RHTTPHeaders hdr = iTransaction.Request().GetHeaderCollection();
    SetHeaderL( hdr, HTTP::EUserAgent, KUserAgent );
    SetHeaderL( hdr, HTTP::EAccept, KAccept );
    if ( aContentType != KNullDesC8 )
        SetHeaderL( hdr, HTTP::EContentType, aContentType );
    if ( aOAuth != KNullDesC8 )
        SetHeaderL( hdr, HTTP::EAuthorization, aOAuth );
    if ( aSupplier != NULL )
        iTransaction.Request().SetBody( *aSupplier );

    // Submit the transaction. After this the framework will give transaction
    // events via MHFRunL and MHFRunError.
    iTransaction.SubmitL();
    iRunning = ETrue;
    }

//////////////////////////////////////////////////////////////////////////
void CHttpClient::CancelTransaction()
    {
    // if we are opening up a GPRS connection.
    if ( iOpeningConnection )
        {
        iConnOpener->Cancel();
        return;
        }

    // HTTP transaction not running
    if ( !iRunning )
        return;

    // Close() also cancels transaction (Cancel() can also be used but
    // resources allocated by transaction must be still freed with Close())
    iTransaction.Close();
    iRunning = EFalse;
    ResetData();
    TRAPD(err, iObserver.CancelledL());
    PanicIfError( err );
    }

//////////////////////////////////////////////////////////////////////////
void CHttpClient::MHFRunL( RHTTPTransaction aTransaction,
    const THTTPEvent& aEvent )
    {
    LOG(EVerbosityHttp, _L("CHttpClient::MHFRunL() - IN, eventStatus = %d."), aEvent.iStatus);
    switch ( aEvent.iStatus )
        {
        case THTTPEvent::EGotResponseHeaders:
            {
            iObserver.ClientEventL( THTTPEvent::EGotResponseHeaders );
            }
            break;
        case THTTPEvent::EGotResponseBodyData:
            {
            MHTTPDataSupplier* body = aTransaction.Response().Body();
            TPtrC8 dataChunk;

            body->GetNextDataPart( dataChunk );
            body->ReleaseData();
            iObserver.ClientBodyReceivedL( dataChunk );
            }
            break;
        case THTTPEvent::EResponseComplete:
            {
            // Indicates that header & body of response is completely received.
            // No further action here needed.
            iObserver.ClientEventL( THTTPEvent::EResponseComplete );
            }
            break;

        case THTTPEvent::ESucceeded:
            {
            // Indicates that transaction succeeded.
            // Transaction can be closed now. It's not needed anymore.
            aTransaction.Close();
            ResetData();
            iRunning = EFalse;
            // After transaction has been closed, leaves are no
            // longer handled by the calling active object.
            TRAPD(err, iObserver.ClientEventL(THTTPEvent::ESucceeded));
            PanicIfError( err );
            }
            break;

        case THTTPEvent::EFailed:
            {
            // Transaction completed with failure.
            TInt errCode = aTransaction.Response().StatusCode();
            aTransaction.Close();
            ResetData();
            iRunning = EFalse;
            // After transaction has been closed, leaves are no
            // longer handled by the calling active object.
            TRAPD(err, iObserver.ClientErrorL(errCode));
            PanicIfError( err );
            }
            break;
        default:
            // There are more events in THTTPEvent, but they are not usually
            // needed. However, event status smaller than zero should be handled
            // correctly since it's error.
            {
            if ( aEvent.iStatus < 0 )
                {
                if ( aEvent.iStatus == -7330 )
                    break;
                // Just close the transaction on errors
                iTransaction.Close();
                ResetData();
                iRunning = EFalse;
                // After transaction has been closed, leaves are no
                // longer handled by the calling active object.
                TRAPD(error, iObserver.ClientErrorL(aEvent.iStatus));
                PanicIfError( error );
                }
            else
                {
                // Other events are not errors (e.g. permanent and temporary
                // redirections)
                }
            }
            break;
        }
    LOG(EVerbosityHttp, _L("CHttpClient::MHFRunL() - Out."));
    }

////////////////////////////////////////////////////////////////////////////////
TInt CHttpClient::MHFRunError( TInt aError, RHTTPTransaction /*aTransaction*/,
    const THTTPEvent& /*aEvent*/)
    {
    // only report about the error.
    TRAPD(error, iObserver.ClientErrorL(aError));
    return error;
    }

////////////////////////////////////////////////////////////////////////////////
TBool CHttpClient::GetCredentialsL( const TUriC8& aURI, RString aRealm,
    RStringF aAuthenticationType, RString& aUsername, RString& aPassword )
    {
    // aURI, aReal and aAuthenticationType are informational only. We only need
    // to set aUsername and aPassword and return ETrue, if aUsername and
    // aPassword are provided by user.

    CSettings& settings = CSettings::InstanceL();
    if ( settings.Username() == KNullDesC || settings.Password() == KNullDesC )
        {
        // Query user name and password
        TBuf<KDefaultBufferSize> userName;
        TBuf<KDefaultBufferSize> password;
        CAknMultiLineDataQueryDialog* dlg = CAknMultiLineDataQueryDialog::NewL(
            userName, password );

        if ( !dlg->ExecuteLD( R_DIALOG_USER_PASSWORD_QUERY ) )
            return EFalse; // No credentials given; must return EFalse

        settings.SetUsername( userName );
        settings.SetPassword( password );
        CSettings::InstanceL().StoreSettingsL();
        }
    // Set aUsername and aPassword
    TBuf8<KDefaultBufferSize> temp;
    temp.Copy( settings.Username() );
    TRAPD(err, aUsername = aRealm.Pool().OpenStringL(temp));
    if ( KErrNone == err )
        {
        temp.Copy( *settings.GeneratePassHashLC() );
        TRAP(err, aPassword = aRealm.Pool().OpenStringL(temp));
        CleanupStack::PopAndDestroy();
        if ( KErrNone == err )
            return ETrue;
        }

    // Return ETrue if user has given credentials (username and password),
    // otherwise EFlase
    return EFalse;
    }

void CHttpClient::RemoveValidationFilter()
    {
    RHTTPFilterCollection filtColl = iHttpSession.FilterCollection();
    THTTPFilterIterator iter = filtColl.Query();

    THTTPFilterRegistration regInfo;
    iter.First();
    RStringF validationName = iHttpSession.StringPool().StringF(
        HTTP::EValidation, RHTTPSession::GetTable() );
    while ( !iter.AtEnd() )
        {
        // Get next filter registration info
        regInfo = iter();

        TBuf<KMaxName> name;
        name.Copy(
            iHttpSession.StringPool().StringF( regInfo.iName ).DesC().Left(
                KMaxName ) );
        TBuf<KMaxName> header;
        header.Copy(
            iHttpSession.StringPool().StringF( regInfo.iHeader ).DesC().Left(
                KMaxName ) );

        RDebug::Print( _L("\n%16S | %4d | %4d | %16S | %3d | %2d"), &name,
            regInfo.iPosition, regInfo.iEvent.iStatus, &header,
            regInfo.iStatus, regInfo.iHandle );
        ++iter;
        if ( filtColl.CanChangeFilters() && validationName
            == iHttpSession.StringPool().StringF( regInfo.iName ) )
            filtColl.RemoveFilter( validationName );
        }
    }

// end of file
